/**
 * @file chat.c
 * @brief Sorgente che racchiude le funzioni per la gestione di una semplice chat.
 *
 * Viene definita la funzione principale che serve da gestore dell'avvio della comunicazione
 * e le sottofunzioni private richiamate per gestire le varie fasi della stessa
 * comunicazione.
 *
 * @date Mar 29, 2012
 * @author Francesco Corvino
 */
#include "cutility.h"
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include "../../RscNet/rscNet.h"
#include "../../RscNet/connection.h"
#include "../../logproxy/logproxy.h"

#define MAXSIZE_NAMES 30
#define MAXSIZE_ADDRESS 30
#define MAXSIZE_PORT 20

static DynArray utenti;
static bool _Chat_isInit = false;
static char _Chat_myName[MAXSIZE_NAMES] = "ChatApp";
static char _Chat_regServer_address[MAXSIZE_ADDRESS] = "localhost";
static char _Chat_regServer_port[MAXSIZE_PORT] = "45678";


typedef struct {
	PAddress paddr;
	Address netaddr;
	char name[30];
} Utente;

int Utenti_cmp(const void *a, const void *b){
	return strcmp(((Utente*)a)->name,((Utente*)b)->name);
}

Utente * Utenti_Add(char *name, char *address, char *port){
	log_namedlevel("chat",LOG_INFO_LEVEL,
			"Richiesta di aggiungere l'utente %s (%s:%s)",name,address,port);
	Utente nuovo,*temp;
	strcpy(nuovo.name,name);
	/// - Copia gli indirizzi dell'utente
	PAddress_initS(&nuovo.paddr,address,port);
	/// - Ricava l'indirizzo di rete
	if(UDP_getAddress(&(nuovo.netaddr),address,port)==-1){
		log_namedlevel("chat",LOG_ERROR_LEVEL,
				"Attenzione errore nel creare l'indirizzo di rete per l'utente %s (%s:%s)",
				name,address,port);
	}
	/// - Controlla se l'utente è già registrato
	if((temp=DynArray_get(&utenti,&nuovo))!=NULL) {
		/// + Se è già registrato aggiorna i valori.
		memcpy(temp,&nuovo,sizeof(Utente));
		return temp;
	}
	/// + Se non è registrato lo aggiunge
	DynArray_add(&utenti,&nuovo);
	log_namedlevel("chat",LOG_INFO_LEVEL,
			"Aggiunto l'utente %s (%s:%s)",name,address,port);
	return DynArray_get(&utenti,&nuovo);
}

void _Chat_Update(){
	Connection c;UDPConnection uc;
	uc.ai_family = AF_UNSPEC;
	uc.isServer = false;
	uc.isConnected = false;
	Connection_init(&c,_Chat_regServer_address,_Chat_regServer_port);
	UDP_createConn(&c,&uc);
	if(c.sd != -1){
		char buf[UDP_MAX_SIZE_PKT];
        /// - Compone il messaggio di aggiornamento.
        char msgUpdate[] = "request=update;\0";
        /// - Effettua la richiesta al server
		UDP_sendMsgTo(&c,&(c.addr),msgUpdate);
		/// - Si mette in attesa della risposta
		int rcvb = UDP_recvMsgFrom(&c,buf,UDP_MAX_SIZE_PKT,&(c.addr));
		if(rcvb>0){
			DynArray params;
			char *line,*pbuf = buf, *bufp = NULL;
			while((line = strtok_r(pbuf, "\n",&bufp))!=NULL){
				DynArray_init(&params,UserParam_cmp,sizeof(UserParam));
				UserParam_fillArrDef(&params,line);
				UserParam *name = UserParam_getFromDynArray(&params,"name");
				UserParam *port = UserParam_getFromDynArray(&params,"port");
				UserParam *address = UserParam_getFromDynArray(&params,"address");
				Utenti_Add(name->value,address->value,port->value);
				DynArray_destroy(&params);
				pbuf=NULL;
			}
		}
	}
	RscNet_unregConnection(c.connectionId);
	Connection_destroy(&c);
}

void _Chat_Init(){
	DynArray_init(&utenti,Utenti_cmp,sizeof(Utente));
	_Chat_Update();
	_Chat_isInit = true;
}

/**
 * @brief Attende un'input dai due file descriptor forniti.
 *
 * @param fd0 primo file descriptor.
 * @param fd1 secondo file descriptor.
 * @return restituisce il valore del file descriptor che si è sboccato o -1
 * se nessuno dei due è attivo.
 */
int _Chat_waitInput(int fd0, int fd1){
	struct timeval tv;
	tv.tv_sec = 1000; tv.tv_usec = 0;
	fd_set fdsetread;
	FD_ZERO(&fdsetread);
	FD_SET(fd0,&fdsetread);
	FD_SET(fd1, &fdsetread);
	int maxfd = (fd0>fd1)?(fd0):(fd1);
	select(maxfd+1,&fdsetread,NULL,NULL,&tv);
	if(FD_ISSET(fd0,&fdsetread)) return fd0;
	else if(FD_ISSET(fd1,&fdsetread)) return fd1;
	return -1;
}

/**
 * @brief Avvia la procedura di scambio dei messaggi con il client.
 *
 * @param c specifica la connessione con cui effettuare la comunicazione.
 * @param sessFd specifica il file descriptor con cui comunicare con la sessione dell'utente.
 * @param client contiene le informazioni per identificare la comunicazione.
 */
void _ChatScambiaMessaggi(Connection *c, int sessFd,Utente *client){
	char msg[1000]; int sizebuff = 1000;
	do {
		memset(msg,0,sizebuff);
		int ret = _Chat_waitInput(c->sd,sessFd);
		if(ret==c->sd){
			UDP_recvMsgFrom(c,msg,sizebuff,&(client->netaddr));
			AppLib_writeToHostF("%s --> %s",client->name,msg);
		} else {
			AppLib_readFromHost(msg,sizebuff);
			UDP_sendMsgTo(c, &(client->netaddr), msg);
		}
	} while (strncmp(msg,"end",3)!=0);
	AppLib_writeToHostF("Sessione di comunicazione %s ternimata!\n",client->name);
}

/**
 * @brief Prova ad iniviare un messaggio verso un'indirizzo.
 */
bool _ChatTryConnection(Connection *c,Utente *client){
	// ricava il messaggio di inizializzazione della chat
	char initMsg[255];
	sprintf(initMsg,"start %s", _Chat_myName);
	// avvia una comunicazione
	if(!UDP_sendMsgTo(c,&(client->netaddr),initMsg)){
		// il messaggio non è stato inviato..
		AppLib_writeToHostF( "Chat con %s non riuscita, impossibile inviare messaggi.",
				client->name);
		return false;
	}
	return true;
}

/**
 * @brief implementa la funzione execute dell'interfaccia comando per avviare una chat.
 */
void exChat_Start(){
	char address[MAXSIZE_ADDRESS], port[MAXSIZE_PORT], name[MAXSIZE_NAMES];
	Utente *client = NULL;
	/// - Controlla se gli utenti sono inizializzati
	if(!_Chat_isInit) _Chat_Init();
	/// - Cerca se il nome è passato come parametro
	int ret = AppLib_getParam("name",name,MAXSIZE_NAMES);
	/// - Controlla se è stato definito e in questo caso:
	if(ret>1) {
		/// + Ricava i dati sul client richiesto
		Utente cercato; strcpy(cercato.name, name);
		client = DynArray_get(&utenti,&cercato);
	} else strcpy(name,CHAT_DEFAULT_NAME);
	/// - Controlla se l'utente è stato individuato, e se non è stato individuato:
	if(client == NULL) {
		/// + recupera le informazioni sull'indirizzo con cui comunicare
		ret = AppLib_getParam("address",address, MAXSIZE_ADDRESS);
		if(ret<2) strcpy(address,CHAT_DEFAULT_ADDRESS);
		/// + recupera le informazioni sulla porta
		ret = AppLib_getParam("port",port, MAXSIZE_PORT);
		if(ret<1) strcpy(port, CHAT_DEFAULT_PORT);
		/// + Controlla se le informazioni recuperate sono corrette provando ad aggiungere il client
		client = Utenti_Add(name,address,port);
	}
	/// - Se l'utente non è stato individuato termina
	if(client == NULL){
		AppLib_writeToHostF("Chat con %s (%s:%s) non riuscita, non riesco ad individuare l'utente.\n",
				name,address,port);
		return;
	}
	/// prende l'indirizzo e la porta validi
	strcpy(address,client->paddr.address);
	sprintf(port,"%u",client->paddr.port);
	/// - Crea la connessione UDP per il client scelto
	Connection c;UDPConnection uc;
	uc.ai_family = AF_UNSPEC;
	uc.isServer = false;
	uc.isConnected = false;
	Connection_init(&c,address,port);
	UDP_createConn(&c,&uc);
	/// - Controlla che la connessione sia stata stabilita
	if(c.sd == -1) {
		AppLib_writeToHostF("Chat con %s non riuscita, errore di connessione per %s:%s\n",
				name, address, port);
		return;
	}
	/// - Prova a inizializzare la connessione scambiando dei messaggi
	if(_ChatTryConnection(&c, client)) {
		/// - Ricava il filedescriptor della sessione con cui scambiare i messaggi.
		int sessionFd = Session_getFDRead(AppLib_getMySession());
		/// - Avvia lo scambio messaggi
		_ChatScambiaMessaggi(&c,sessionFd,client);
	}
	/// - Elimina la connessione
	RscNet_unregConnection(c.connectionId);
	Connection_destroy(&c);
}

void exChat_Update(){
	AppLib_writeToHostF("Richiedo gli aggiornamenti dal server..\n");
	if(_Chat_isInit)_Chat_Update();
	else _Chat_Init();
	int i = 0;Utente *utente;
	DYNARRAY_FOREACH(&utenti,&i,utente){
		AppLib_writeToHostF("Utente registrato %s -> %s:%d \n",
				utente->name,utente->paddr.address, utente->paddr.port);
	}
	if(i==0){
		AppLib_writeToHostF("Nessun utente registrato!\n");
	} else {
		AppLib_writeToHostF("Totale utenti registrati: %d\n",i);
	}
}

void exChat_Configure(){
	char temp[255];
	AppLib_writeToHostF("Sintassi: regAddress=<indirizzoServer>;regPort=<portaServer>;\n");
	int ret = AppLib_getParam("regAddress",temp, MAXSIZE_ADDRESS);
	if(ret>3) strcpy(_Chat_regServer_address,temp);
	ret = AppLib_getParam("regPort",temp,MAXSIZE_PORT);
	if(ret>2) strcpy(_Chat_regServer_port,temp);
	AppLib_writeToHostF("Register Server: %s:%s\n",_Chat_regServer_address,_Chat_regServer_port);
}
